<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Custom GTK+ widget</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="cairo, graphics, C, 2D vector library, Custom GTK+ widget">
<meta name="description" content="Custom GTK+ widget">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Custom GTK+ widget</h1>


<p>
In this part of the GTK+ programming tutorial, we will create a custom GTK+ widget, 
where we will use the Cairo graphics library. 
</p>

<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>

<h2>CPU widget</h2>

<p>
In the next example we will create a CPU widget. 
</p>

<pre class="code">
/* cpu.h */

#ifndef __CPU_H
#define __CPU_H

#include &lt;gtk/gtk.h&gt;
#include &lt;cairo.h&gt;

G_BEGIN_DECLS


#define GTK_CPU(obj) GTK_CHECK_CAST(obj, gtk_cpu_get_type (), GtkCpu)
#define GTK_CPU_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, gtk_cpu_get_type(), GtkCpuClass)
#define GTK_IS_CPU(obj) GTK_CHECK_TYPE(obj, gtk_cpu_get_type())


typedef struct _GtkCpu GtkCpu;
typedef struct _GtkCpuClass GtkCpuClass;


struct _GtkCpu {
  GtkWidget widget;

  gint sel;
};

struct _GtkCpuClass {
  GtkWidgetClass parent_class;
};


GtkType gtk_cpu_get_type(void);
void gtk_cpu_set_sel(GtkCpu *cpu, gint sel);
GtkWidget * gtk_cpu_new();


G_END_DECLS

#endif /* __CPU_H */
</pre>

<pre class="code">
/* cpu.c */

#include "cpu.h"


static void gtk_cpu_class_init(GtkCpuClass *klass);
static void gtk_cpu_init(GtkCpu *cpu);
static void gtk_cpu_size_request(GtkWidget *widget,
    GtkRequisition *requisition);
static void gtk_cpu_size_allocate(GtkWidget *widget,
    GtkAllocation *allocation);
static void gtk_cpu_realize(GtkWidget *widget);
static gboolean gtk_cpu_expose(GtkWidget *widget,
    GdkEventExpose *event);
static void gtk_cpu_paint(GtkWidget *widget);
static void gtk_cpu_destroy(GtkObject *object);


GtkType
gtk_cpu_get_type(void)
{
  static GtkType gtk_cpu_type = 0;


  if (!gtk_cpu_type) {
      static const GtkTypeInfo gtk_cpu_info = {
          "GtkCpu",
          sizeof(GtkCpu),
          sizeof(GtkCpuClass),
          (GtkClassInitFunc) gtk_cpu_class_init,
          (GtkObjectInitFunc) gtk_cpu_init,
          NULL,
          NULL,
          (GtkClassInitFunc) NULL
      };
      gtk_cpu_type = gtk_type_unique(GTK_TYPE_WIDGET, &amp;gtk_cpu_info);
  }


  return gtk_cpu_type;
}

void
gtk_cpu_set_state(GtkCpu *cpu, gint num)
{
   cpu->sel = num;
   gtk_cpu_paint(GTK_WIDGET(cpu));
}


GtkWidget * gtk_cpu_new()
{
   return GTK_WIDGET(gtk_type_new(gtk_cpu_get_type()));
}


static void
gtk_cpu_class_init(GtkCpuClass *klass)
{
  GtkWidgetClass *widget_class;
  GtkObjectClass *object_class;


  widget_class = (GtkWidgetClass *) klass;
  object_class = (GtkObjectClass *) klass;

  widget_class->realize = gtk_cpu_realize;
  widget_class->size_request = gtk_cpu_size_request;
  widget_class->size_allocate = gtk_cpu_size_allocate;
  widget_class->expose_event = gtk_cpu_expose;

  object_class->destroy = gtk_cpu_destroy;
}


static void
gtk_cpu_init(GtkCpu *cpu)
{
   cpu->sel = 0;
}


static void
gtk_cpu_size_request(GtkWidget *widget,
    GtkRequisition *requisition)
{
  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_CPU(widget));
  g_return_if_fail(requisition != NULL);

  requisition->width = 80;
  requisition->height = 100;
}


static void
gtk_cpu_size_allocate(GtkWidget *widget,
    GtkAllocation *allocation)
{
  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_CPU(widget));
  g_return_if_fail(allocation != NULL);

  widget->allocation = *allocation;

  if (GTK_WIDGET_REALIZED(widget)) {
     gdk_window_move_resize(
         widget->window,
         allocation->x, allocation->y,
         allocation->width, allocation->height
     );
   }
}


static void
gtk_cpu_realize(GtkWidget *widget)
{
  GdkWindowAttr attributes;
  guint attributes_mask;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_CPU(widget));

  GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);

  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = 80;
  attributes.height = 100;

  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;

  attributes_mask = GDK_WA_X | GDK_WA_Y;

  widget->window = gdk_window_new(
     gtk_widget_get_parent_window (widget),
     &amp; attributes, attributes_mask
  );

  gdk_window_set_user_data(widget->window, widget);

  widget->style = gtk_style_attach(widget->style, widget->window);
  gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL);
}


static gboolean
gtk_cpu_expose(GtkWidget *widget,
    GdkEventExpose *event)
{
  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_CPU(widget), FALSE);
  g_return_val_if_fail(event != NULL, FALSE);

  gtk_cpu_paint(widget);

  return FALSE;
}


static void
gtk_cpu_paint(GtkWidget *widget)
{
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_translate(cr, 0, 7);

  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_paint(cr);

  gint pos = GTK_CPU(widget)->sel;
  gint rect = pos / 5;

  cairo_set_source_rgb(cr, 0.2, 0.4, 0);

  gint i;
  for ( i = 1; i <= 20; i++) {
      if (i > 20 - rect) {
          cairo_set_source_rgb(cr, 0.6, 1.0, 0);
      } else {
          cairo_set_source_rgb(cr, 0.2, 0.4, 0);
      }
      cairo_rectangle(cr, 8, i*4, 30, 3);
      cairo_rectangle(cr, 42, i*4, 30, 3);
      cairo_fill(cr);
  }

  cairo_destroy(cr);
}


static void
gtk_cpu_destroy(GtkObject *object)
{
  GtkCpu *cpu;
  GtkCpuClass *klass;

  g_return_if_fail(object != NULL);
  g_return_if_fail(GTK_IS_CPU(object));

  cpu = GTK_CPU(object);

  klass = gtk_type_class(gtk_widget_get_type());

  if (GTK_OBJECT_CLASS(klass)->destroy) {
     (* GTK_OBJECT_CLASS(klass)->destroy) (object);
  }
}
</pre>

<pre class="code">
/* main.c */

#include "cpu.h"


static void set_value(GtkWidget * widget, gpointer data)
{
  GdkRegion *region;

  GtkRange *range = (GtkRange *) widget;
  GtkWidget *cpu = (GtkWidget *) data;
  GTK_CPU(cpu)->sel = gtk_range_get_value(range);

  region = gdk_drawable_get_clip_region(cpu->window);
  gdk_window_invalidate_region(cpu->window, region, TRUE);
  gdk_window_process_updates(cpu->window, TRUE);
}


int main (int argc, char ** argv)
{
  GtkWidget *window;
  GtkWidget *cpu;
  GtkWidget *fixed;
  GtkWidget *scale;

  gtk_init(&amp;argc, &amp;argv);


  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "CPU widget");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 200, 180);


  g_signal_connect(G_OBJECT(window), "destroy", 
       G_CALLBACK(gtk_main_quit), NULL);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  cpu = gtk_cpu_new();
  gtk_fixed_put(GTK_FIXED(fixed), cpu, 30, 40);


  scale = gtk_vscale_new_with_range(0.0, 100.0, 1.0);
  gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
  gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_TOP);
  gtk_widget_set_size_request(scale, 50, 120);
  gtk_fixed_put(GTK_FIXED(fixed), scale, 130, 20);

  g_signal_connect(G_OBJECT(scale), "value_changed", 
      G_CALLBACK(set_value), (gpointer) cpu);

  gtk_widget_show(cpu);
  gtk_widget_show(fixed);
  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}
</pre>

<p>
The CPU widget is a <b class="keyword">GtkWidget</b>, on which we draw with Cairo API.
We draw a black background and 40 small rectangles. The rectangles are drawn in two colors.
Dark green and bright green color. The <b class="keyword">GtkVScale</b> widget controls the number
of the bright green rectangles drawn on the widget.
</p>

<p>
The example might look difficult at the first sight. But it is not 
that difficult after all. Most of the 
code is boilerplate, it always repeats, when we create a new widget.
</p>

<p>
The drawing is done within the <b class="keyword">gtk_cpu_paint()</b> function.
</p>

<pre class="explanation">
cairo_t *cr;

cr = gdk_cairo_create(widget->window);

cairo_translate(cr, 0, 7);

cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
</pre>

<p>
As usual, we create a cairo context. We shift the origin 7 unit down. 
Next we paint the background of the widget to black color.
</p>

<pre class="explanation">
gint pos = GTK_CPU(widget)->sel;
gint rect = pos / 5;
</pre>

<p>
Here we retrieve the sel number. It is the number that we got from the 
scale widget. The slider has 100 numbers. The rect parameter makes a conversion 
from slider values into rectangles, that will be drawn in bright green color.
</p>

<pre class="explanation">
gint i;
for ( i = 1; i <= 20; i++) {
    if (i > 20 - rect) {
        cairo_set_source_rgb(cr, 0.6, 1.0, 0);
    } else {
        cairo_set_source_rgb(cr, 0.2, 0.4, 0);
    }
    cairo_rectangle(cr, 8, i*4, 30, 3);
    cairo_rectangle(cr, 42, i*4, 30, 3);
    cairo_fill(cr);
}
</pre>

<p>
Depending on the rect number, we draw 40 rectangles in two dark 
green color or in bright green color. Remember, that we are 
drawing these rectangles from top to bottom. 
</p>


<pre class="explanation">
GtkRange *range = (GtkRange *) widget;
GtkWidget *cpu = (GtkWidget *) data;
GTK_CPU(cpu)->sel = gtk_range_get_value(range);
</pre>

<p>
In the <b class="keyword">set_value()</b> call, we get the reference to the 
CPU widget and set the sel value to current value, selected on the scale widget.
</p>


<pre class="explanation">
GdkRegion *region;
...
region = gdk_drawable_get_clip_region(cpu->window);
gdk_window_invalidate_region(cpu->window, region, TRUE);
gdk_window_process_updates(cpu->window, TRUE);
</pre>

<p>
This code invalidates the complete window of the CPU widget and 
thus makes it redraw itself.
</p>


<br>
<img src="/img/gui/cgtk/cpuwidget.png" alt="cpu widget">
<div class="figure">Figure: CPU widget</div>


<hr class="btm">

<p>
In this chapter we have created a custom GTK+ widget.
</p>

<br>
<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div>
<br>

<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified March 16, 2008  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>
